'''
pyDuinoAPI : Python Arduino API
author:     Steve De Jongh ( dejongh[dot]st[at]gmail[dot]com )
Licence type: MIT

Copyright (c) 2013 Steve De Jongh (dejongh[dot]st[at]gmail[dot]com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''

from comport import ComPort
from pinout import Pinout
from arduinocommand import ArduinoCommand
from constants import *

PINOUT_UNO = [("A0","analog"),("A1","analog"),("A2","analog"),("A3","analog"),("A4","analog"),("A5","analog"),(2,"digital"),(3,"pwm"),(4,"digital"),(5,"pwm"),(6,"pwm"),(7,"digital"),(8,"digital"),(9,"pwm"),(10,"pwm"),(11,"pwm"),(12,"digital"),(13,"digital")]

class Arduino(object):
    """
    Arduino board class.
    Usage: myArduino = Arduino(model,comport[,speed])
    model: is one of the following: "uno" (more to come ...)
    comport: is the name of the COM port ie: "COM3" for Windows environment
    speed: bits rate in bits/sec (default= 115200 bits/s) The Arduino should use the same speed in Serial.begin(...)
    """
    def __init__(self,model,comport,speed=115200):
        self._modelPinouts = { "uno":PINOUT_UNO }
        if model in self._modelPinouts.keys():
            self._pinout = Pinout(self._modelPinouts[model])
        else:
            self._pinout = Pinout(self._modelPinouts["uno"])

        self._analogPins = self._pinout.listPins("analog")
        self._digitalPins = self._pinout.listPins("digital")
        self._pwmPins = self._pinout.listPins("pwm")

        self._comport = comport
        self._speed = speed
        self._connection = ComPort(comport,speed,self.parseMsg)
        self._sequenceNumber = 1

    def start(self):
        """
        Usage: myArduino.start()
        Launches communication with the Arduino
        """
        self._connection.start()

    def close(self):
        """
        Usage: myArduino.close()
        Properly close serial connection to free COM port
        """
        self._connection.terminate()

    def pinMode(self,pin,mode):
        """
        Usage: Arduino.pinMode(myPin,pinmode)
        Sets the working mode of a pin. Can be either INPUT or OUTPUT
        Works only for Digital and PWM pins. If applied on analog pins, will simply be ignored.
        """
        if pin in self._pinout.listPins("digital") or pin in self._pinout.listPins("pwm"):
            pinObj = self._pinout[pin]
            if mode == INPUT:
                pinObj.inputMode()
                # SEND SERIAL COMMAND
            elif mode == OUTPUT:
                pinObj.outputMode()
                # SEND SERIAL COMMAND
            else:
                # Incorrect mode
                pass
        else:
            # Pin either doesn't exist or is not a digital pin.
            pass

    def digitalWrite(self,pin,value):
        """
        Usage: Arduino.digitalWrite(pin,value)
        value can be HIGH or LOW.
        Works for all digital pins including PWM
        """
        if pin in self._pinout.listPins("digital") or pin in self._pinout.listPins("pwm"):
            pinObj = self._pinout[pin]
            if value == HIGH:
                pinObj.setHigh()
                # SEND SERIAL COMMAND
            elif value == LOW:
                pinObj.setLow()
                # SEND SERIAL COMMAND
            else:
                # Incorrect value
                pass
        else:
            # Pin either doesn't exist or is not a digital pin.
            pass

    def analogWrite(self,pin,value):
        """
        Usage: Arduino.analogWrite(pin,value)
        value can range from 0 to 255 (integer)
        Works only for PWM pins
        """
        if pin in self._pinout.listPins("pwm") and value >= 0 and value <= 255:
            pinObj = self._pinout[pin]
            pinObj.setValue(int(value))
            # SEND SERIAL COMMAND
        else:
            # Error in value or pin doesn't support PWM
            pass

    def digitalRead(self,pin):
        """
        Usage: value = Arduino.digitalRead(pin)
        returns value of the given pin.
        1023 = HIGH
        0 = LOW
        """
        if (pin in self._pinout.listPins("digital") or pin in self._pinout.listPins("pwm")) and self._pinout[pin].getMode() == INPUT:
            pinObj = self._pinout[pin]
            return pinObj.read()
        else:
            # Pin isn't digital
            pass

    def analogRead(self,pin):
        """
        Usage: value = Arduino.analogRead(pin)
        returns value of the given pin ranging from 0 to 1023
        """
        if pin in self._pinout.listPins("analog"):
            pinObj = self._pinout[pin]
            return pinObj.read()
        else:
            # Pin isn't analogic
            pass

    def enablePullup(self,pin):
        """
        Usage: Arduino.enablePullup(pin)
        Enable internal pullup resistor for digital pins used as INPUT only
        """
        if (pin in self._pinout.listPins("digital") or pin in self._pinout.listPins("pwm")) and self._pinout[pin].getMode() == INPUT:
            pinObj = self._pinout[pin]
            pinObj.pullup(True)
            # SEND SERIAL COMMAND
        else:
            # Pin isn't digital or ...
            pass

    def disablePullup(self,pin):
        """
        Usage: Arduino.disablePullup(pin)
        Disable internal pullup resistor for digital pins used as INPUT only
        """
        if (pin in self._pinout.listPins("digital") or pin in self._pinout.listPins("pwm")) and self._pinout[pin].getMode() == INPUT:
            pinObj = self._pinout[pin]
            pinObj.pullup(False)
            # SEND SERIAL COMMAND
        else:
            # Pin isn't digital or ...
            pass

    def enablePin(self,pin):
        """
        Usage: Arduino.enablePin(pin)
        Defines a Pin as in use (makes Arduino reports its state)
        Only useful for INPUT pins
        """
        if (pin in self._pinout.listPins()):
            pinObj = self._pinout[pin]
            pinObj.used()
            # SEND SERIAL COMMAND
        else:
            # Pin doesn't exist
            pass

    def disablePin(self,pin):
        """
        Usage: Arduino.enablePin(pin)
        Defines a Pin as unused (makes Arduino stops reporting its state)
        Only useful for INPUT pins
        """
        if (pin in self._pinout.listPins()):
            pinObj = self._pinout[pin]
            pinObj.unused()
            # SEND SERIAL COMMAND
        else:
            # Pin doesn't exist
            pass

    def isAnalog(self,pin):
        """
        Usage: Arduino.isAnalog(pin)
        Returns True if the pin is analog, else will return false
        """
        return pin in self._analogPins

    def isDigital(self,pin):
        """
        Usage: Arduino.isDigital(pin)
        Returns True if the pin is digital (including PWM pins), else will return false
        """
        return (( pin in self._digitalPins ) or ( pin in self._pwmPins ))

    def isPwm(self,pin):
        """
        Usage: Arduino.isPwm(pin)
        Returns True if the pin supports PWM, else will return false
        """
        return pin in self._pwmPins
        

    def parseMsg(self,msg):
        """
        Usage: myArduino.parseMsg(message)
        Parses messages received from Arduino
        """
        msgArray = msg.split('/')

        msgTypes = {
            'R':self._dataReceived,
            'A':self._ackReceived
        }

        msgType = msgArray.pop(0)
        if msgType in msgTypes.keys():
            msgTypes[msgType](msgArray)
        else:
            print "Unknow msg type: " + msgType

    def _dataReceived(self,data):
        """
        Usage: myArduino._dataReceived(cmdArgList)
        Treat data received from Arduino (msg type: R/<pin>/<data>[/<pin>/<data>...])
        """  
        for i in range(0,len(data)/2):
            pin = data[i*2]
            value = data[i*2+1]
            if pin[0] not in ('A','D'):
                print "Error during communication: ",data
            #print pin,value    

    def _ackReceived(self,data):
        """
        Usage: myArduino._ackReceived(ackArgList)
        Treat ack received from Arduino (msg type: A/seqNbr/...)
        """
        pass

    def _sendCmd(self,cmd):
        """
        Usage: myArduino._sendCmd(cmdString)
        Sends the command string to the cArduino
        """
        self._connection.write(cmd)

    def _getSequence(self):
        """
        Usage: seq = myArduino._getSequence()
        Returns the actual sequence number and increments it for the next command
        """
        seq = self._sequenceNumber
        self._sequenceNumber += 1
        return seq

###########
#TEST CODE#
###########
import time
if __name__ == "__main__":
    print "1...Creating Arduino Object"
    myArduino = Arduino('uno','COM3',115200)
    print "2...Starting communication"
    print "3...Sending some commands"
    myArduino.start()
    myArduino._sendCmd(ArduinoCommand(CMD_WRITE,1,'A0',255))
    time.sleep(15)
    print "4...Stopping communication"
    myArduino.close()

    
